package com.bqnxzz.ywiigame.util;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Date;
import java.util.Enumeration;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

import com.bqnxzz.ywiigame.MainActivity;

public class HotUpdateUtil {

    private static final String TAG = "H5_HotUpdateUtil";

    public static String preloadPath = "www_root/";

    public static String getLocalGamesPath() {
        return preloadPath;
    }

    private static String getLocalVersionFileName() {
        return getLocalGamesPath() + "hall/manifest.json";
    }

    public static String getAppInfoFileName() {
        return getLocalGamesPath() + "app.localinfo";
    }

    /**
     * 本地当前的版本号
     */
    static JSONObject localversion;

    /**
     * 本地当前的app信息
     * {
     * crc32:"",
     * downloadedGameIds:["DDZ", "ZJH],
     * readyToDownloadGameIds:["",""]
     * }
     */
    public static JSONObject appinfo = new JSONObject();

    private static long unzipedFileCount = 0;
    private static long maxFileCount = 0;

    private static long hall_crc32 = 0L;
    private static boolean hall_shell = true;

    private static MThread thread;

    /**
     * 使用CheckedInputStream计算CRC
     */
    public static Long getCRC32() {
        long tm = (new Date()).getTime();
        CRC32 crc32 = new CRC32();
        try {
            InputStream ipt = MainActivity.self.getAssets().open("rusu.ru");
            byte[] buffer = new byte[8192];
            int length;
            while ((length = ipt.read(buffer)) != -1) {
                crc32.update(buffer, 0, length);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return -1L;
        } catch (IOException e) {
            e.printStackTrace();
            return -1L;
        }
        Log.d(TAG, (tm - (new Date()).getTime()) + "");
        return crc32.getValue();
    }

    /**
     * 检查js库更新
     */
    public static void checkJSPatch() {
        if (thread != null) {
            return;
        }

        File file = MainActivity.self.getFilesDir();
        String path = file.getAbsolutePath();
        preloadPath = path + "/" + preloadPath;

        if (hall_crc32 == 0) {
            hall_crc32 = getCRC32();
        }

        thread = new MThread(() -> {
            //读取本地版本信息
            boolean readComplete = readLocalVersionAndUnzip();
            if (!readComplete) {
                switchToMainThread(-1);
                return;
            }

            Log.d(TAG, "JS版本是最新的");
            switchToMainThread(0);
        });

        thread.start();
    }

    /**
     * 回到主UI
     *
     * @param state -1 初次解压game.zip失败
     *              -2 获取远程版本信息version_list文件失败
     *              -3 下载/解压补丁包失败
     */
    private static void switchToMainThread(final int state) {
        MainActivity.self.runOnUiThread(() -> {
            int changedState = state;

            // 好消息好消息，只有初次解压失败才中断app流程，强制提示退出。
            // 其他如“无法获取远程版本信息文件sversion_list.json”、“无法下载热更新包”的情况下，均可以正常进入登录页面，由服务器去效验版本号以及是否允许继续畅玩游戏
            if (changedState == -2 || changedState == -3 || changedState == -4) {
                changedState = 0;
            }

            if (changedState < 0) {
                String text;
                if (changedState == -1) {
                    text = "初次解压失败，请检查手机容量后再试。";
                } else if (changedState == -2) {
                    text = "获取最新版本信息失败，请检查网络，稍后再试。";
                } else if (changedState == -3) {
                    text = "下载/解压补丁包失败，请检查手机容量并检查网络，稍后再试。";
                } else if (changedState == -4) {
                    text = "获取更新线路失败，请检查网络，稍后再试。";
                } else {
                    text = "未知错误，请检查手机容量并检查网络，稍后再试。";
                }
                new AlertDialog.Builder(MainActivity.self)
                        .setTitle("警告")
                        .setMessage(text)
                        .setPositiveButton("确定", (dialog, which) -> System.exit(0))
                        .create().show();
                MainActivity.self.preloadingProxy.stopAndError(text);
            } else {
                MainActivity.self.preloadingProxy.startInit();
                MainActivity.self.startNative();
            }
        });
    }

    /**
     * 读取本地version文件
     */
    private static boolean readLocalVersionAndUnzip() {
        String versionFile = getLocalVersionFileName();
        File file = new File(versionFile);
        File appLocalInfo = new File(getAppInfoFileName());

        Boolean isLastZip = false;
        if (file.exists() && appLocalInfo.exists()) {
            try {
                String str = readFile(file);
                Log.d(TAG, "本地版本号信息: " + str);
                localversion = JSONObject.parseObject(str);

                String appStr = readFile(appLocalInfo);
                Log.d(TAG, "本地app信息:" + appStr);
                appinfo = JSONObject.parseObject(appStr);
                if (appinfo == null) {
                    appinfo = new JSONObject();
                }

                if (appinfo.getLongValue("crc32") == hall_crc32) {
                    isLastZip = true;
                }
            } catch (Exception e) {
                Log.d(TAG, "读取本地版本号失败" + versionFile, e);
                e.printStackTrace();
                return false;
            }
        }

        if (isLastZip) {
            if (localversion != null) {
                MainActivity.self.preloadingProxy.updateNowVersion(localversion.getString("v"));
                return true;
            } else {
                Log.d(TAG, "本地版本号信息解析失败:");
                return false;
            }
        } else {
            //设置已经下载过的游戏，需要重新下载
            setDownloadedGameIdDirty();

            Log.d(TAG, "本地版本号文件不存在,开始初次解压");

            try {
                MainActivity.self.preloadingProxy.unzipProgress(0);
                unzipFirstInGame();

                appinfo.put("crc32", hall_crc32);
                appinfo.put("shell", hall_shell);
                writeAppInfoToFile();

                return readLocalVersionAndUnzip();
            } catch (Exception e) {
                Log.d(TAG, "初次解压game.zip失败", e);
                e.printStackTrace();
                return false;
            }
        }
    }

    private static void setDownloadedGameIdDirty() {
        if (HotUpdateUtil.appinfo.containsKey("downloadedGameIds")) {
            JSONArray downloadedGameIds = HotUpdateUtil.appinfo.getJSONArray("downloadedGameIds");

            HotUpdateUtil.appinfo.put("readyToDownloadGameIds", downloadedGameIds);
            HotUpdateUtil.appinfo.remove("downloadedGameIds");

            Log.d(TAG, "需要重新下载游戏包:" + downloadedGameIds.toJSONString());
        }
        HotUpdateUtil.writeAppInfoToFile();
    }

    private static void unzipFirstInGame() throws IOException {
        String unzipPath = getLocalGamesPath() + "hall/";

        InputStream ipt = MainActivity.self.getAssets().open("rusu.ru");

        //文件个数
        maxFileCount = getZipTrueSize();

        if (maxFileCount <= 0) {
            maxFileCount = 1;
        }

        unZipGameFiles(ipt, unzipPath);

        //如果有allGame.gam，则同时解压所有的游戏
        InputStream allGames = null;
        try {
            allGames = MainActivity.self.getAssets().open("allGame.gam");
        } catch (Exception e) {
        }
        if (allGames != null) {
            //解压默认涵盖在包里面的游戏
            unZipGameFiles(allGames, getLocalGamesPath());

            //记录本地存在的游戏包
            File file = new File(getLocalGamesPath());
            File[] files = file.listFiles();
            if (files != null) {
                JSONArray downloadedGameIds = new JSONArray();
                for (File file1 : files) {
                    if (file1.isDirectory()) {
                        String fileName = file1.getName();

                        if (!fileName.equals("hall")) {
                            fileName = fileName.toUpperCase();
                            downloadedGameIds.add(fileName);
                        }
                    }
                }
                appinfo.put("downloadedGameIds", downloadedGameIds);
                writeAppInfoToFile();
            }
        }
    }

    public static void writeAppInfoToFile() {
        String localStr = JSONObject.toJSONString(appinfo);
        Log.d(TAG, "更新app.localinfo：" + localStr);

        File file = new File(getLocalGamesPath());
        if (!file.exists()) {
            file.mkdir();
        }
        writeFile(localStr, getAppInfoFileName());
    }

    /**
     * 读TXT文件内容
     */
    public static String readFile(File fileName) throws Exception {
        if (!fileName.exists()) {
            return "";
        }
        StringBuilder result = new StringBuilder();
        FileReader fileReader = null;
        BufferedReader bufferedReader = null;
        try {
            fileReader = new FileReader(fileName);
            bufferedReader = new BufferedReader(fileReader);
            try {
                String read;
                while ((read = bufferedReader.readLine()) != null) {
                    result.append(read).append("\r\n");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                bufferedReader.close();
            }
            if (fileReader != null) {
                fileReader.close();
            }
        }
        return result.toString();
    }

    /**
     * 写TXT文件内容
     */
    private static void writeFile(String content, String fileName) {
        FileOutputStream o;
        try {
            o = new FileOutputStream(fileName);
            o.write(content.getBytes("UTF-8"));
            o.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 解压到指定目录
     */
    private static void unZipBatchFiles(String zipPath, String descDir) throws IOException {
        unZipBatchFiles(new File(zipPath), descDir);
    }

    /**
     * 解压文件到指定目录
     */
    @SuppressWarnings("rawtypes")
    private static void unZipBatchFiles(File zipFile, String descDir) throws IOException {
        File pathFile = new File(descDir);
        if (!pathFile.exists()) {
            pathFile.mkdirs();
        }
        ZipFile zip = new ZipFile(zipFile);
        for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) {
            ZipEntry entry = (ZipEntry) entries.nextElement();
            String zipEntryName = entry.getName();
            InputStream in = zip.getInputStream(entry);
            String outPath = (descDir + zipEntryName).replaceAll("\\*", "/");

            outPath = outPath.replaceAll(preloadPath + "/", preloadPath);
            //判断路径是否存在,不存在则创建文件路径
            File file = new File(outPath.substring(0, outPath.lastIndexOf('/')));
            if (!file.exists()) {
                file.mkdirs();
            }
            //判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
            if (new File(outPath).isDirectory()) {
                continue;
            }
            //输出文件路径信息
            Log.d(TAG, "解压文件：" + outPath);

            MainActivity.self.preloadingProxy.updateInfo("解压文件：" + zipEntryName);

            OutputStream out = new FileOutputStream(outPath);
            byte[] buf1 = new byte[1024];
            int len;
            while ((len = in.read(buf1)) > 0) {
                out.write(buf1, 0, len);
            }
            in.close();
            out.close();
        }
        Log.d(TAG, "补丁解压完!");
    }

    /**
     * 解压文件到指定目录
     */
    @SuppressWarnings("rawtypes")
    private static void unZipGameFiles(InputStream zipFile, String descDir) throws IOException {
        // 创建解压目标目录
        File targetPath = new File(descDir);
        // 如果目标目录不存在，则创建
        if (!targetPath.exists()) {
            targetPath.mkdirs();
        }

        ZipInputStream zipInputStream = new ZipInputStream(zipFile);

        // 读取一个进入点
        ZipEntry zipEntry = zipInputStream.getNextEntry();
        // 使用1Mbuffer
        byte[] buffer = new byte[1024 * 1024];
        // 解压时字节计数
        int count;
        // 如果进入点为空说明已经遍历完所有压缩包中文件和目录
        while (zipEntry != null) {
            // 如果是一个目录
            if (zipEntry.isDirectory()) {
                // 如果是目录
                File file = new File(descDir + File.separator + zipEntry.getName());
                // 防止压缩路径遍历漏洞
                String canonicalPath = file.getCanonicalPath();
                if (!canonicalPath.startsWith(targetPath.getCanonicalPath() + File.separator)) {
                    continue;
                }
                // 文件需要覆盖或者是文件不存在
                file.mkdir();
            } else {
                unzipedFileCount++;

                // 如果是文件
                File file = new File(descDir + File.separator + zipEntry.getName());
                // 防止压缩路径遍历漏洞
                String canonicalPath = file.getCanonicalPath();
                if (!canonicalPath.startsWith(targetPath.getCanonicalPath() + File.separator)) {
                    continue;
                }
                // 文件需要覆盖或者文件不存在，则解压文件
                file.createNewFile();
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                while ((count = zipInputStream.read(buffer)) > 0) {
                    fileOutputStream.write(buffer, 0, count);
                }
                fileOutputStream.close();

                MainActivity.self.preloadingProxy.unzipProgress((int) (unzipedFileCount * 100L / maxFileCount));

            }
            // 定位到下一个文件入口
            zipEntry = zipInputStream.getNextEntry();
        }
        zipInputStream.close();

        Log.d(TAG, "初次解压完!");
    }

    /**
     * 获取压缩包解压后的内存大小
     */
    private static long getZipTrueSize() {
        // 获取assets中的txt文件
        String s = "";
        try {
            BufferedReader br;
            InputStream is;

            is = MainActivity.self.getAssets().open("rus.txt");
            br = new BufferedReader(new InputStreamReader(is));
            s = br.readLine();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return Integer.parseInt(s);
    }

    private static class MThread extends Thread {
        Handler handler;

        MThread(Runnable runnable) {
            super(runnable);
        }

        @SuppressLint("HandlerLeak")
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            handler = new Handler() {

                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                }

            };
            Looper.loop();
        }
    }

}
